<?php

namespace Celebrio\FileSystem\Drivers;

use Nette\Object;
use Nette\Environment;

use Celebrio\FileSystem\Drivers\IDriver;
use Celebrio\FileSystem\DataFile;
use Celebrio\FileSystem\File;
use Celebrio\FileSystem\Drivers\AzureCelebrioDatabase;
use Celebrio\FileSystem\IOException;

use Celebrio\WindowsAzure\AzureStorage\AzureStorageBLOB;

use \Celebrio\AccessControl\AlbireoUser;

use \FileNotFoundException;
use \InvalidArgumentException;

use \Microsoft_WindowsAzure_Storage_Blob;
use \Microsoft_Http_Client_Adapter_Exception;
use \Microsoft_WindowsAzure_Exception;

use Celebrio\Diagnostics\Timer;
use Celebrio\Diagnostics\TimerLevel;



/**
 * Azure driver
 *
 * @author honza
 */
class AzureCelebrio extends AzureStorageDriver implements IDriver {
    const NAME = "azureCelebrio";
    const HOST = "blob.core.windows.net";

    protected static $host = "blob.core.windows.net";
    protected static $accountName;
    protected static $password;

    private $userName = NULL;
    private $dbDriver = NULL;
    //This will be company name or some other specifier
    private $prefix = 'celebrio';


    /* ------------------------------------------------------- */

    public function __construct() {
        $this->dbDriver = new AzureCelebrioDatabase();
    }

    public function load(DataFile $dataFile, $stream = true) {
        Timer::start("AzureDrv - load-all", TimerLevel::OVERVIEW);
        if ($dataFile->isDirectory()) {
            throw new InvalidArgumentException("It is not allowed to load directory through driver", 0);
        }

        $owner = $this->getOwnerForFile($dataFile);
        $path = $this->getPathForFile($dataFile);
        $path = \rtrim($path); //Temporary till database on azure wont be dropped
        try {
            if ($stream) {
                AzureStorageBLOB::connection()->registerStreamWrapper();
                //NOTE: azure:// is initiated by registreStreamWrapper one row up
                $data = fopen("azure://" . $owner . "/" . $path, "r");
                AzureStorageBLOB::connection()->unregisterStreamWrapper();
            } else {
                $data = AzureStorageBLOB::connection()->getBlobData($owner, $path);
            }
        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            throw new IOException("Connection failed. Check internet connection.", 0, $e);
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            throw new FileNotFoundException("Can not load the file", 0, $e);
        }
        $result = $this->makeDataFile($dataFile);
        $result->setData($data);
        
        Timer::stop("AzureDrv - load-all");

        return $result;
    }

    public function save(DataFile $dataFile) {
        Timer::start("AzureDrv - save-all", TimerLevel::OVERVIEW);
        if (!is_resource($dataFile->getData()) && \strlen($dataFile->getData()) >= self::MAX_BLOB_SIZE) {
            $this->saveLarge($dataFile);
            return;
        }

        $owner = $this->getOwnerForFile($dataFile);
        $path = $this->getPathForFile($dataFile);
        $data = $dataFile->getData();

        try {
            AzureStorageBLOB::connection()->createContainerIfNotExists($owner);
            AzureStorageBLOB::connection()->createResourceName($owner, $path);
            if (is_resource($dataFile->getData())) {
                $handler = $dataFile->getData();
                AzureStorageBLOB::connection()->registerStreamWrapper();
                fseek($handler, 0);
                $file = fopen("azure://" . $owner . "/" . $path, "w");
                while (!feof($handler)) {
                    $data = fread($handler, 1 * (1024 * 1024));
                    fwrite($file, $data);
                    ob_flush();
                    flush();
                }
                fclose($handler);
                fclose($file);
                AzureStorageBLOB::connection()->unregisterStreamWrapper();
                return;
            } else {
                AzureStorageBLOB::connection()->putBlobData($owner, $path, $data);
            }
        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            throw new \Celebrio\FileSystem\IOException("Connection failed. Check internet connection.", 0, $e);
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            throw new FileNotFoundException("Problem with storing file", 0, $e);
        }
        
        Timer::stop("AzureDrv - save-all");
    }


    private function saveLargeFile(DataFile $dataFile) {
        if (\strlen($dataFile->getData()) < self::MAX_BLOB_SIZE) {
            $this->save($dataFile);
            return;
        }

        $owner = $this->getOwnerForFile($dataFile);
        $path = $this->getPathForFile($dataFile);
        $data = $dataFile->getData();

        try {
            AzureStorageBLOB::connection()->createContainerIfNotExists($owner);
            $numOfBlocks = ceil(\strlen($data) / self::MAX_BLOCK_SIZE);
            $indexes = $this->getIndexes($numOfBlocks);

            //storing blocks on server
            for ($i = 0; $i < $numOfBlocks; $i++) {
                $blockData = \substr($data, $i * self::MAX_BLOCK_SIZE, self::MAX_BLOCK_SIZE);
                AzureStorageBLOB::connection()->putBlock($owner, $path, $indexes[$i], $blockData);
                $blockData = null;
                unset($blockData);
            }
            //commit blocks
            AzureStorageBLOB::connection()->putBlockList($owner, $path, $indexes);

        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            throw new IOException("Connection failed. Check internet connection.", 0, $e);
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            throw new FileNotFoundException("Problem with storing large object", 0, $e);
        }
    }

    public function delete(DataFile $dataFile) {
        $owner = $this->getOwnerForFile($dataFile);
        $path = $this->getPathForFile($dataFile);

        try {
            AzureStorageBLOB::connection()->deleteBlob($owner, $path);
        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            throw new IOException("Connection failed. Check internet connection.", 0, $e);
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            throw new FileNotFoundException("File not found", 0, $e);
        }
    }

    public function exists(DataFile $dataFile) {
        Timer::start("AzureDrv - exists", TimerLevel::OVERVIEW);
        $owner = $this->getOwnerForFile($dataFile);
        $path = $this->getPathForFile($dataFile);

        try {
            $exists = AzureStorageBLOB::connection()->blobExists($owner, $path);
        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            throw new IOException("Connection failed. Check internet connection.", 0, $e);
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            throw new FileNotFoundException("File not found", 0, $e);
        }
        Timer::stop("AzureDrv - exists");
        return $exists;
    }

    public function move(DataFile $source, DataFile $destination) {

        //This driver does nothing on move
    }

    public function onChangeOwner(DataFile $datafile) {
        //Here will be code which will change some things
    }

    /**
     * Deletes container based on user id
     * @param AlbireoUser $user 
     */
    public function cleanUser(AlbireoUser $user) {
        try {
            $containerName = $this->prefix . "-" . $user->getId();
            $exists = AzureStorageBLOB::connection()->containerExists($containerName);
            if($exists) {
                AzureStorageBLOB::connection()->deleteContainer($containerName);
            }
        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            throw new IOException("Connection failed. Check internet connection.", 0, $e);
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            throw new FileNotFoundException("File not found", 0, $e);
        }
    }

    private function getOwnerForFile(DataFile $dataFile) {
        Timer::start("AzureDrv - getOwner", TimerLevel::OVERVIEW);
        $owner_id = $this->dbDriver->getOwnerId($dataFile);
        if (!$owner_id) {
            throw new \Celebrio\FileSystem\FileNotFoundException("The file is not assigned to any user");
        }
        $owner_id = $this->prefix . "-" . $owner_id;
        
        Timer::stop("AzureDrv - getOwner");
        if (Microsoft_WindowsAzure_Storage_Blob::isValidContainerName($owner_id)) {
            return $owner_id;
        } else {
            //This should never happen due to user name policy
            throw new InvalidArgumentException("The name for container is not valid.", 0);
        }
    }

    protected function getPathForFile(DataFile $dataFile) {
        return $this->dbDriver->getOrMakeRemoteName($dataFile);
    }

    private function makeDataFile(DataFile $dataFile) {
        $result = new DataFile(
            $dataFile->getName(),
            $dataFile->getUrl(),
            $dataFile->getMimetypeText(),
            $dataFile->getFstorageName()
        );

        return $result;
    }

    public static function setConnectionSettings($accountName, $password) {
        self::$accountName = $accountName;
        self::$password = $password;
    }

    public static function areCredentialsValid($accountName, $password) {
        $azure = new \Microsoft_WindowsAzure_Storage_Blob(self::HOST, $accountName, $password);
        try {
            $azure->listContainers();
            return true;
        } catch (Microsoft_Http_Client_Adapter_Exception $e) {
            return false;
        } catch (\Microsoft_WindowsAzure_Exception $e) {
            return false;
        }
    }


}